9. Безопасность GraphQL, авторизация, IAM Keycloak
В данном разделе мы рассмотрим основы безопасности GraphQL, авторизации и IAM Keycloak. Также будут даны рекомендации по интеграции механизмов безопасности GraphQL (проверка и фильтрация) в React-приложение медицинской клиники.
Основы авторизации
Современные веб-приложения требуют надежных механизмов контроля доступа для защиты данных и обеспечения персонализированного опыта пользователей. В контексте нашего React-приложения медицинской клиники особенно важно разграничить доступ между различными ролями пользователей.
Ключевые концепции
-
Аутентификация — процесс проверки подлинности пользователя (кто это?). Аутентификация проверяет, что пользователь действительно является тем, за кого он себя выдает.
-
Авторизация — процесс определения прав доступа пользователя к ресурсам (что может делать?). Авторизация проверяет, что пользователь имеет право выполнять определенные действия над определенными ресурсами.
При этом оба процесса взаимосвязаны и не могут быть разделены. Аутентификация проверяет, кто пользователь, а авторизация проверяет, что пользователь может делать.
Современный подход к авторизации
Для обеспечения безопасности и удобства в нашем приложении мы будем использовать стандартные инструменты:
- OpenID Connect для аутентификации пользователей
- JWT токены для передачи информации о правах доступа и аутентификации
- Keycloak как централизованный сервер управления идентификацией и авторизацией

Хоть приложение данного учебного курса и носит демонстрационный характер, но в реальных проектах используются аналогичные инструменты и подходы. Это обеспечивает масштабируемость, безопасность и простоту интеграции компонентов системы. Рассмотрим каждый из этих инструментов более подробно.
OpenID Connect

OpenID Connect (OIDC) — это протокол аутентификации, построенный поверх OAuth 2.0. Он позволяет приложениям безопасно проверять личность пользователя и получать базовую информацию о его профиле.
Основные принципы OpenID Connect:
- Единый вход (Single Sign-On) — пользователь авторизуется один раз и получает доступ ко всем подключенным приложениям
- Стандартизированный протокол — использует проверенные механизмы OAuth 2.0 с добавлением слоя идентификации
- JWT токены — информация о пользователе передается в виде JSON Web Token, который можно проверить без обращения к серверу авторизации
- Безопасность — все данные передаются по защищенным каналам, токены имеют ограниченное время жизни
Участники процесса:
- Identity Provider (IdP) — сервер авторизации (например, Keycloak), который проверяет учетные данные пользователя
- Relying Party (RP) — клиентское приложение (например, React-приложение), которое доверяет IdP
- End User — пользователь, который проходит аутентификацию
OpenID Connect упрощает интеграцию авторизации в современные веб-приложения, обеспечивая высокий уровень безопасности и удобство для пользователей.
JWT (JSON Web Token)

JWT (JSON Web Token) — это компактный и безопасный способ передачи информации между сторонами в виде JSON-объекта. JWT широко используется для авторизации в веб-приложениях и API.
Структура JWT токена:
JWT состоит из трех частей, разделенных точками: header.payload.signature
- Header (заголовок) — содержит информацию о типе токена и алгоритме подписи
- Payload (полезная нагрузка) — содержит данные о пользователе (claims): ID, роли, время истечения, email
- Signature (подпись) — криптографическая подпись, которая гарантирует целостность токена
Пример JWT токена:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJzdWIiOiJ1c2VyMSIsInJvbGVzIjpbImFkbWluaXN0cmF0b3IiXSwiZW1haWwiOiJ1c2VyMUBtYWlsLnJ1IiwiZXhwIjoxNzM4MjU2NDAwfQ.signature-hash-here
Для анализа JWT токена можно воспользоваться онлайн-сервисом: https://jwt.io/, перейдите по ссылке и вставьте токен в поле JSON Web Token в левой части страницы. В правой части страницы будет отображены все данные токена.

- Header:
{
"alg": "RS256",
"typ": "JWT",
"kid": "123"
}
Здесь alg - алгоритм подписи, typ - тип токена, kid - идентификатор ключа.
- Payload:
{
"sub": "user1",
"roles": [
"administrator"
],
"email": "user1@mail.ru",
"exp": 1738256400
}
Здесь sub - идентификатор пользователя, roles - роли пользователя, email - email пользователя, exp - время истечения токена, указано в секундах с 1970 года.
Основные преимущества JWT:
- Самодостаточность — токен содержит всю необходимую информацию, не требуя обращения к базе данных
- Безопасность — подпись предотвращает подделку токена
- Компактность — небольшой размер токена позволяет передавать его в HTTP заголовках
- Стандартизация — открытый стандарт (RFC 7519), поддерживаемый всеми языками программирования
Жизненный цикл JWT:
- Создание — сервер авторизации создает и подписывает токен
- Передача — клиент получает токен и сохраняет его
- Использование — токен отправляется с каждым запросом в заголовке Authorization
- Проверка — сервер проверяет подпись и валидность токена
- Истечение — токен автоматически становится недействительным через определенное время
JWT обеспечивает надежную и эффективную авторизацию без необходимости хранения состояния на сервере.
IAM Keycloak

Keycloak — это открытый проект, который предоставляет единую платформу для управления доступом к приложениям и сервисам. Он позволяет контролировать доступ пользователей к различным ресурсам, включая веб-приложения, мобильные приложения и API. KeyCloak является свободным ПО с открытым кодом, распространяемым по лицензии Apache License 2.0. Разработан компанией RedHat, Inc. В этом разделе мы рассмотрим основы Keycloak и его практическое применение в проекте React-приложения медицинской клиники.
Основные объекты Keycloak:
-
Realm - область приложения, рабочее пространство, в которой пользователи могут быть авторизованы и получать доступ к различным ресурсам. Realm позволяет настраивать внешний вид страницы логина для каждой отдельной области. Кроме того, в каждом realm возможна регистрация пользователей, авторизация через социальные сети, Single Sign-On/Sign-Off для всех приложений данного realm, выдача JSON Web Token подлинности аккаунтам, двухфакторная аутентификация и интеграция со службами каталогов (LDAP-сервером). Таким образом, realms в Keycloak обеспечивают гибкость и масштабируемость системы безопасности для различных приложений и сервисов.
-
Client - приложение или система, которые использует Keycloak для аутентификации своих пользователей. Клиенты могут быть настроены в Keycloak путем создания нового приложения или выбора существующего. Это позволяет клиентам использовать функции Keycloak, такие как аутентификация, управление пользователями и ролью, а также другие возможности безопасности. Клиент в Keycloak может быть подключен к серверу для предоставления доступа к своим ресурсам пользователям, которые были успешно аутентифицированы Keycloak.
-
User - пользователь системы, который проходит процесс аутентификации и получает доступ к ресурсам. Пользователь обычно имеет уникальный идентификатор и учётную запись, позволяющую ему пройти аутентификацию и получить разрешение на доступ к определённым ресурсам.
-
Role - роль пользователя в системе. Роли определяют права и привилегии, которыми обладает пользователь, и позволяют управлять доступом к ресурсам. Role может быть назначена конкретному пользователю или группе пользователей, она определяет, какие действия они могут выполнять в рамках системы.
Далее будет рассмотрена локальная установка KeyCloak, создание демо-данных для приложения медицинской клиники, а также получение JWKS-ключа KeyCloak для обеспечения проверки подписи JWT-токенов. На основе полученных данных будет рассмотрена безопасность GraphQL и интеграция механизмов безопасности в React-приложение.
Лолальный IAM-сервер KeyCloak

Установка
-
Скачайте KeyCloak: https://github.com/keycloak/keycloak/releases/download/26.2.4/keycloak-26.2.4.zip
-
Распакуйте архив и перейдите в директорию
keycloak-26.2.4, далее запустите KeyCloak:
./bin/kc.sh start-dev --http-port=8180
Запуск KeyCloak на порту 8180 производится с целью избежать конфликта портов с DataSpace CE, который запускается на порту 8080. В реальном проекте KeyCloak может быть запущен на любом свободном порту.
-
Откройте панель управления KeyCloak http://localhost:8180/
-
Система предложит создать учетную запись администратора, укажите логин и парль, например
admin 12345
Создание демо-данных KeyCloak
Для демонстрации механизмов безопасности далее будет создан набор демо-данных приложения медицинской клиники.
- Создайте новый realm с именем
clinic

- Создайте новый client с именем
clinicи укажите для него параметрroot url:http://localhost:3000/

- Создайте три
client roles:-
administrator (администратор клиники)
- может просматривать и изменять все данные в приложении
-
doctor (доктор)
- просмотр записей кто к нему записался
- просмотр исследований его пациентов и результатов этих исследований
-
patient (пациент)
- Просмотр доступности врача (любого)
- Запись на прием
-

Важно, что мы создаем именно client roles, а не realm roles. Realm roles используются для управления доступом к ресурсам в рамках всего KeyCloak, а client roles используются для управления доступом к ресурсам в рамках конкретного клиента. Это позволяет более гибко управлять доступом к ресурсам в рамках конкретного приложения, а также обеспечивает безопасность и масштабируемость системы.
-
Создайте трех демо-пользователей:
user1 user1@mail.ruuser2 user2@mail.ruuser3 user3@mail.ru
-
Создайте пароли для пользователей и отключите их временный характер:

- Присвойте пользователям user1, user2, user3 соответствующие client role:
user1 -> administratoruser2 -> doctoruser3 -> patient

Получение JWKS-ключа KeyCloak:
JWKS (JSON Web Key Set) — это файл, содержащий открытые ключи для проверки подписи JWT токенов. Этот файл критически важен для работы авторизации в связке KeyCloak + DataSpace CE. Он позволяет DataSpace проверять подлинность JWT-токенов, которые будет отправлять React-приложение.
Как это работает:
- KeyCloak создает JWT-токен → подписывает его ПРИВАТНЫМ ключом
- React приложение получает JWT-токен → отправляет в GraphQL запросах
- DataSpace CE получает JWT-токен → проверяет подпись ОТКРЫТЫМ ключом из jwks.json
- Если подпись верна → пользователь авторизован, иначе → отказ в доступе
JWKS в DataSpace CE:
В файле context-child.properties (dataspace-ce/files/resources/src-model/) есть важная настройка:
dataspace.security.jwks.source=file. По умолчанию данный параметр отключен (закомментирован).
Для настройки использования JWKS-ключа из файла в DataSpace CE выполните следующие действия:
-
Расскоментируйте параметр
dataspace.security.jwks.source=fileв файле context-child.properties (dataspace-ce/files/resources/src-model/). Это будет указывать DataSpace CE что при проверке подписи JWT-токенов GraphQL-заппросов необходимо использовать ключ из файлаjwks.json. -
Перейдите по адресу: http://localhost:8180/realms/clinic/protocol/openid-connect/certs В случае возникновения ошибки проверьте что KeyCloak запущен на порту
8180. -
Cкопируйте полученный JWKS-ключ в файл
dataspace-ce/files/resources/src-model/jwks.jsonПри необходимости можно произвести визуальноеформатирование ключа для лучше понимания его структуры. -
Перезапустите DataSpace CE для применения настроек, выполнив в корневой директории DataSpace CE:
./quickstart.sh
JWKS-ключ содержит массив ключей, каждый из которых имеет свой уникальный идентификатор kid. Таким образом, DataSpace CE может использовать этот ключ для проверки подписи JWT-токенов, отправленных React-приложением. Это позволяет обеспечить безопасность и целостность данных при передаче между компонентами приложения.
Безопасность GraphQL
Иногда приемка созданного приложения у эксперта кибербезопасности можеть быть непростой задачей:

GraphQL предоставляет гибкие возможности для запросов данных, но как при этом обеспечить безопасность, разграничение доступа и фильтрацию данных?
RBAC и ABAC
GraphQL предоставляет гибкие возможности для запросов данных, но при этом требует надежных механизмов контроля доступа. В системах безопасности современных приложений используются два основных подхода: RBAC (Role-Based Access Control) и ABAC (Attribute-Based Access Control).
RBAC (Role-Based Access Control)

RBAC — это модель контроля доступа, основанная на ролях пользователей. В этой модели права доступа определяются ролью, которая назначается пользователю.
Принципы RBAC:
- Роли — определяют набор разрешений (администратор, врач, пациент)
- Пользователи — получают роли в соответствии со своими обязанностями
- Разрешения — привязаны к ролям, а не к конкретным пользователям
- Иерархия — роли могут наследовать права друг от друга
Пример RBAC в GraphQL-разрешениях:
{
"name": "viewAllPatients",
"checkSelects": [
{
"conditionValue": "'administrator' $in ${jwt:roles}",
"typeName": null
}
]
}
ABAC (Attribute-Based Access Control)

ABAC — это более гибкая модель контроля доступа, основанная на атрибутах субъекта, объекта, действия и окружения.
Компоненты ABAC:
- Субъект — пользователь и его атрибуты (роль, отдел, специализация)
- Объект — ресурс и его атрибуты (тип данных, владелец, конфиденциальность)
- Действие — операция (чтение, запись, удаление)
- Окружение — контекст (время, IP-адрес, способ аутентификации)
Пример ABAC в GraphQL-разрешениях:
{
"name": "viewPatientRecords",
"checkSelects": [
{
"conditionValue": "'doctor' $in ${jwt:roles} && ${jwt:specialization} == ${object.patientType} && ${environment.hour} >= 8 && ${environment.hour} <= 18",
"typeName": "PatientRecord"
}
],
"pathConditions": [
{
"path": "searchPatientRecord",
"cond": "it.assignedDoctor.entityId == ${jwt:clinicDoctorId} && it.createdDate >= ${environment.workingHoursStart}"
}
]
}
Гибридный подход в DataSpace CE
В реальных медицинских системах часто используется гибридный подход, сочетающий RBAC и ABAC:
1. Базовый уровень (RBAC):
{
"name": "searchDoctorAppointmentForDoctor",
"checkSelects": [
{
"conditionValue": "'doctor' $in ${jwt:roles}",
"typeName": null
}
]
}
2. Дополнительная фильтрация (ABAC):
{
"pathConditions": [
{
"path": "searchDoctorAppointment",
"cond": "it.doctorSchedule.clinicDoctor.entityId == ${jwt:clinicDoctorId} && it.beginDate >= ${environment.currentDate}"
}
]
}
Возможные практические примеры для медицинской клиники
Сценарий 1: Врач просматривает свои записи
{
"name": "getDoctorOwnAppointments",
"body": "query getDoctorOwnAppointments { ... }",
"checkSelects": [
{
"conditionValue": "'doctor' $in ${jwt:roles}",
"typeName": null
}
],
"pathConditions": [
{
"path": "searchDoctorAppointment",
"cond": "it.doctorSchedule.clinicDoctor.entityId == ${jwt:clinicDoctorId} && it.status != 'CANCELLED'"
}
]
}
Сценарий 2: Администратор с ограничениями по времени
{
"name": "adminViewReports",
"checkSelects": [
{
"conditionValue": "'administrator' $in ${jwt:roles} && ${jwt:workingHours} == true",
"typeName": null
}
]
}
Сценарий 3: Пациент видит только своих врачей
{
"name": "patientViewAssignedDoctors",
"checkSelects": [
{
"conditionValue": "'patient' $in ${jwt:roles}",
"typeName": null
}
],
"pathConditions": [
{
"path": "searchDoctor",
"cond": "it.doctorAppointmentList{cond = it.clinicCustomer.customer.entityId == ${jwt:customerId}}.$exists"
}
]
}
Преимущества каждого подхода
RBAC:
- ✅ Простота реализации и управления
- ✅ Легкость понимания для администраторов
- ✅ Хорошая масштабируемость для типовых сценариев
- ❌ Ограниченная гибкость для сложных правил
ABAC:
- ✅ Высокая гибкость и точность контроля
- ✅ Поддержка сложных бизнес-правил
- ✅ Возможность динамических разрешений
- ❌ Сложность в настройке и отладке
В медицинских системах гибридный подход оптимален: базовые роли определяют общие права доступа, а атрибуты позволяют точно настроить доступ к конфиденциальным медицинским данным с учетом специфики лечебного процесса.
Файл graphql-permissions.json
Файл graphql-permissions.json является важным компонентом системы безопасности DataSpace CE, который определяет разрешения для выполнения GraphQL-операций.
Назначение и принципы работы
DataSpace CE использует подход "по умолчанию всё запрещено" — это означает, что при включении параметра dataspace.security.graphql.permissions.source=file в файле context-child.properties (dataspace-ce/files/resources/src-model/) без явного разрешения в файле graphql-permissions.json никакая GraphQL-операция не может быть выполнена. Каждая операция должна:
- Быть зарегистрирована в реестре допустимых операций
- Иметь уникальное имя — анонимные запросы запрещены
- Пройти проверки безопасности — CheckSelects и PathConditions
Структура файла конфигурации
Файл представляет собой JSON-массив объектов, каждый из которых описывает разрешенную операцию:
[
{
"name": "имя_операции",
"body": "полное тело GraphQL запроса",
"allowEmptyChecks": true/false,
"disableJwtVerification": true/false,
"checkSelects": [
{
"conditionValue": "условие проверки",
"typeName": "тип сущности"
}
],
"pathConditions": [
{
"path": "путь к полю",
"cond": "условие фильтрации"
}
]
}
]
Основные параметры конфигурации
name— уникальное имя GraphQL-операции (обязательно)body— полный текст GraphQL-запроса для сверки (обязательно)allowEmptyChecks— разрешает пустые проверки безопасностиdisableJwtVerification— отключает проверку JWT (анонимный доступ)checkSelects— массив проверок, выполняемых перед операциейpathConditions— дополнительные условия фильтрации данных
Пример конфигурации для медицинской клиники
[
{
"name": "searchDoctorsForPatient",
"body": "query searchDoctorsForPatient($cond: String) { searchDoctor(cond: $cond) { elems { id person { entity { firstName lastName } } doctorType { name } } } }",
"allowEmptyChecks": false,
"disableJwtVerification": false,
"checkSelects": [
{
"conditionValue": "'patient' $in ${jwt:roles}",
"typeName": null
}
],
"pathConditions": []
},
{
"name": "searchDoctorAppointmentForDoctor",
"body": "query searchDoctorAppointmentForDoctor($cond: String!) { searchDoctorAppointment(cond: $cond) { elems { id beginDate endDate clinicCustomer { entity { customer { entity { person { entity { firstName lastName birthDate } } } } } } } } }",
"allowEmptyChecks": false,
"disableJwtVerification": false,
"checkSelects": [
{
"conditionValue": "'doctor' $in ${jwt:roles}",
"typeName": null
}
],
"pathConditions": [
{
"path": "searchDoctorAppointment",
"cond": "it.doctorSchedule.clinicDoctor.entityId == ${jwt:clinicDoctorId}"
}
]
},
{
"name": "createDoctorSchedule",
"body": "mutation createDoctorSchedule($input: _CreateDoctorScheduleInput!) { packet { lockClinicSchedule: getClinicSchedule(id:`${input.clinicSchedule}` lock:WAIT) {id} checkDoctorAndOfficeFreeSlot: getClinicSchedule(id:`find: it.id == ${input.clinicSchedule} && !it.doctorScheduleList{cond = (it.clinicDoctor.entityId == ${input.clinicDoctor.entityId} || it.clinicOffice.entityId == ${input.clinicOffice.entityId}) && it.beginDate <= ${input.endDate} && it.endDate >= ${input.beginDate}}.$exists` failOnEmpty:true) {id} createDoctorSchedule(input: $input) {id} } }",
"allowEmptyChecks": false,
"disableJwtVerification": false,
"checkSelects": [
{
"conditionValue": "'administrator' $in ${jwt:roles} || ${input.clinicDoctor} == ${jwt:clinicDoctorId}",
"typeName": null
}
],
"pathConditions": []
}
]
Типы проверок безопасности
1. CheckSelects — проверки, выполняемые перед операцией:
- Проверяют права пользователя на выполнение операции
- Могут использовать данные из JWT-токена
- При неудачной проверке операция блокируется с ошибкой
2. PathConditions — фильтрация данных:
- Накладывают дополнительные условия на выборку данных
- Ограничивают видимые пользователю данные
- Не вызывают ошибок, но могут возвращать пустой результат
Использование JWT-токенов в условиях
В строковых выражениях можно использовать подстановки из JWT:
{
"conditionValue": "'doctor' $in ${jwt:roles} && ${jwt:clinicId} == it.clinic.entityId"
}
Где:
${jwt:roles}— массив ролей пользователя${jwt:clinicId}— ID клиники пользователя${jwt:clinicDoctorId}— ID врача (для роли doctor)
Настройка в DataSpace CE
- Включите безопасность в файле
context-child.properties:
dataspace.security.graphql.permissions.source=file
dataspace.security.jwks.source=file
-
Добавьте в файл
graphql-permissions.jsonразрешения для всех операций, которые будут выполняться в приложении. -
Перезапустите DataSpace CE для применения настроек:
./quickstart.sh
Рекомендации по безопасности
- Принцип минимальных привилегий: предоставляйте только необходимые права
- Валидация входных данных: всегда проверяйте параметры запросов
- Регулярное обновление: актуализируйте разрешения при изменении бизнес-логики
Правильно настроенный файл graphql-permissions.json обеспечивает надежную защиту данных медицинской клиники, разграничивая доступ между администраторами, врачами и пациентами согласно их ролям и полномочиям.
Проверки
Проверки (CheckSelects) — это механизм контроля доступа, который выполняется до выполнения GraphQL-операции. Если проверка не пройдена, операция блокируется с ошибкой авторизации. Это критически важный элемент безопасности, который предотвращает несанкционированный доступ к данным и операциям.
Принципы работы проверок
Последовательность выполнения:
- Пользователь отправляет GraphQL-запрос с JWT-токеном
- DataSpace CE проверяет подпись токена и извлекает данные пользователя
- Выполняются все условия из массива
checkSelects - Если все проверки пройдены успешно — операция выполняется
- Если хотя бы одна проверка провалена — возвращается ошибка
403 Forbidden
Типы проверок:
- Проверка ролей — проверяет наличие определенной роли у пользователя
- Проверка владельца — проверяет, что пользователь имеет право на данный ресурс
- Проверка контекста — проверяет дополнительные условия (время, статус и т.д.)
- Комбинированные проверки — сочетают несколько условий с логическими операторами
Практический пример: создание расписания врача
1. Фиксируем тело запроса в реестре допустимых
mutation createDoctorSchedule($input: _CreateDoctorScheduleInput!) {
packet {
lockClinicSchedule: getClinicSchedule(
id:`${input.clinicSchedule}`
lock:WAIT
) {id}
checkDoctorAndOfficeFreeSlot: getClinicSchedule(
id:`find:
it.id == ${input.clinicSchedule} &&
!it.doctorScheduleList{cond = (
it.clinicDoctor.entityId == ${input.clinicDoctor.entityId}
|| it.clinicOffice.entityId == ${input.clinicOffice.entityId}
) && it.beginDate <= ${input.endDate} && it.endDate >= ${input.beginDate}
}.$exists`
failOnEmpty:true
) {id}
createDoctorSchedule(input: $input) {id}
}
}
Разбор запроса:
lockClinicSchedule— блокирует расписание клиники для предотвращения конфликтовcheckDoctorAndOfficeFreeSlot— проверяет, что врач и кабинет свободны в указанное времяcreateDoctorSchedule— создает новую запись в расписании врача
2. Формируем условие ПРОВЕРКИ
'administrator' $in ${jwt:roles} || ${input.clinicDoctor} == ${jwt:clinicDoctorId}
Логика проверки:
- Администратор (
'administrator' $in ${jwt:roles}) может создавать расписание для любого врача - Врач (
${input.clinicDoctor} == ${jwt:clinicDoctorId}) может создавать расписание только для себя - Пациент не имеет права выполнять эту операцию (проверка провалится)
Дополнительные примеры проверок
Проверка временных ограничений:
{
"conditionValue": "'administrator' $in ${jwt:roles} && ${environment.currentHour} >= 8 && ${environment.currentHour} <= 18",
"typeName": null
}
Проверка статуса пользователя:
{
"conditionValue": "'doctor' $in ${jwt:roles} && ${jwt:status} == 'ACTIVE' && ${jwt:license} == 'VALID'",
"typeName": null
}
Проверка принадлежности к клинике:
{
"conditionValue": "'patient' $in ${jwt:roles} && ${jwt:clinicId} == ${input.clinicId}",
"typeName": null
}
Типы условий в проверках
1. Проверка ролей:
"'administrator' $in ${jwt:roles}" // Пользователь является администратором
"'doctor' $in ${jwt:roles}" // Пользователь является врачом
"'patient' $in ${jwt:roles}" // Пользователь является пациентом
2. Сравнение значений:
"${jwt:clinicDoctorId} == ${input.doctorId}" // ID врача в токене совпадает с входным параметром
"${jwt:customerId} == ${input.customerId}" // ID клиента в токене совпадает с входным параметром
3. Логические операторы:
"'admin' $in ${jwt:roles} || 'doctor' $in ${jwt:roles}" // ИЛИ
"'doctor' $in ${jwt:roles} && ${jwt:status} == 'ACTIVE'" // И
"!('patient' $in ${jwt:roles})" // НЕ
Фильтрация
Фильтрация данных (PathConditions) — это механизм безопасности, который автоматически ограничивает данные, возвращаемые пользователю, в зависимости от его прав доступа. В отличие от проверок (CheckSelects), фильтрация не блокирует операцию, а незаметно применяет дополнительные условия к выборке данных, обеспечивая принцип "нужно знать" (need-to-know).
Основные принципы фильтрации
Механизм работы:
- Пользователь выполняет GraphQL-запрос на получение данных
- DataSpace CE проверяет наличие PathConditions для данной операции
- Автоматически добавляет условия фильтрации к запросу
- Возвращает только те данные, которые пользователь имеет право видеть
- Пользователь не видит данных, к которым у него нет доступа
Ключевые отличия от проверок:
- Проверки → блокируют операцию при неудаче (
403 Forbidden) - Фильтрация → ограничивает данные, операция выполняется успешно
Преимущества подхода:
- Прозрачность — пользователь не знает о существовании скрытых данных
- Безопасность — предотвращает случайное раскрытие конфиденциальной информации
- Гибкость — позволяет тонко настраивать видимость данных
Практический пример: просмотр записей врача
1. Фиксируем тело запроса в реестре допустимых
query searchDoctorAppointmentForDoctor($cond: String!){
searchDoctorAppointment(cond: $cond){
elems{
id
beginDate
endDate
clinicCustomer{
entity{
customer{
entity{
person{
entity{
firstName
lastName
birthDate
}}}}}
}
}
}
}
Разбор запроса:
searchDoctorAppointment— основной запрос для поиска записей к врачуcond— параметр для дополнительной фильтрации (например, по дате)- Вложенная структура — получаем информацию о пациенте через связанные сущности
- Безопасность — фильтрация гарантирует, что врач увидит только свои записи
2. Формируем условие ФИЛЬТРАЦИИ по полю searchDoctorAppointment
it.doctorSchedule.clinicDoctor.entityId == ${jwt:clinicDoctorId}
Логика фильтрации:
it— каждая запись в результате поискаit.doctorSchedule.clinicDoctor.entityId— ID врача из записи${jwt:clinicDoctorId}— ID врача из JWT-токена- Результат: врач видит только свои записи, даже если не указал это в параметре
cond
Сценарии применения фильтрации
Сценарий 1: Многопользовательская система
Без фильтрации врач мог бы увидеть записи всех врачей:
# Опасный запрос без фильтрации
query getAllAppointments {
searchDoctorAppointment {
elems {
id
doctorSchedule { clinicDoctor { entity { person { entity { firstName lastName } } } } }
clinicCustomer { entity { customer { entity { person { entity { firstName lastName } } } } } }
}
}
}
С фильтрацией автоматически применяется условие:
{
"path": "searchDoctorAppointment",
"cond": "it.doctorSchedule.clinicDoctor.entityId == ${jwt:clinicDoctorId}"
}
Сценарий 2: Иерархическая фильтрация
Администратор видит всё, врач — только своё:
{
"path": "searchDoctorAppointment",
"cond": "'administrator' $in ${jwt:roles} || it.doctorSchedule.clinicDoctor.entityId == ${jwt:clinicDoctorId}"
}
Сценарий 3: Временная фильтрация
Врач видит только актуальные записи:
{
"path": "searchDoctorAppointment",
"cond": "it.doctorSchedule.clinicDoctor.entityId == ${jwt:clinicDoctorId} && it.beginDate >= ${environment.currentDate}"
}
Сложные условия фильтрации
Фильтрация с множественными условиями:
{
"path": "searchPatientRecord",
"cond": "('doctor' $in ${jwt:roles} && it.attendingDoctor.entityId == ${jwt:clinicDoctorId}) || ('patient' $in ${jwt:roles} && it.patient.entityId == ${jwt:customerId}) || 'administrator' $in ${jwt:roles}"
}
Фильтрация с проверкой статуса:
{
"path": "searchDoctorSchedule",
"cond": "it.clinicDoctor.entityId == ${jwt:clinicDoctorId} && it.status == 'ACTIVE' && it.beginDate >= ${environment.today}"
}
Комбинирование проверок и фильтрации
Часто используется комбинированный подход:
{
"name": "getDoctorPatients",
"body": "query getDoctorPatients { searchDoctorAppointment { elems { clinicCustomer { entity { customer { entity { person { entity { firstName lastName } } } } } } } } }",
"checkSelects": [
{
"conditionValue": "'doctor' $in ${jwt:roles}",
"typeName": null
}
],
"pathConditions": [
{
"path": "searchDoctorAppointment",
"cond": "it.doctorSchedule.clinicDoctor.entityId == ${jwt:clinicDoctorId} && it.status == 'CONFIRMED'"
}
]
}
Логика работы:
- CheckSelects проверяет, что пользователь — врач
- PathConditions ограничивает данные только записями этого врача со статусом "CONFIRMED"
Безопасность фильтрации
Критически важные правила:
-
Никогда не полагайтесь только на клиентскую фильтрацию — всегда применяйте PathConditions на сервере
-
Тестируйте граничные случаи — убедитесь, что фильтрация работает для всех ролей и сценариев
-
Используйте принцип минимальных привилегий — показывайте только необходимые данные
-
Проверяйте цепочки связей — убедитесь, что через связанные сущности нельзя получить доступ к запрещенным данным
Фильтрация является критически важным компонентом безопасности медицинских систем, обеспечивая автоматическое разграничение доступа к конфиденциальным данным пациентов и соблюдение принципов врачебной тайны.
Рекомендации по интеграции механизмов безопасности в React-приложение
В данном подразделе будет предложен вариант интеграции механизма безапасности GraphQL (проверка и фильтрация) в React-приложение. Используя данные рекомендации и AI-ассистент GigaCode, вы сможете самостоятельно реализовать механизмы безопасности в своем приложении медицинской клиники.
Интеграция механизма проверок в React-приложение
Для успешной интеграции механизма проверок безопасности в React-приложение медицинской клиники необходимо выполнить несколько ключевых шагов, которые обеспечат корректную работу авторизации на всех уровнях системы.
1. Настройка конфигурации Keycloak в React-приложении
В корневой папке public размещается файл конфигурации keycloak.json, который содержит параметры подключения к серверу авторизации:
{
"realm": "clinic",
"auth-server-url": "http://localhost:8180/",
"ssl-required": "external",
"resource": "clinic",
"public-client": true,
"confidential-port": 0,
"useResourceRoleMappings": true
}
Параметр useResourceRoleMappings: true критически важен, так как указывает системе использовать client roles (роли клиента) вместо realm roles. Это позволяет получать роли administrator, doctor и patient, назначенные пользователям user1, user2 и user3 соответственно.
2. Создание GraphQL-запроса в React-приложении
В приложении создается GraphQL-мутация для создания расписания врача. Важно, чтобы тело запроса точно совпадало с тем, что указано в файле graphql-permissions.json:
// src/graphql/__generate/createDoctorSchedule.graphql
mutation createDoctorSchedule($input: _CreateDoctorScheduleInput!) {
packet {
lockClinicSchedule: getClinicSchedule(
id:`${input.clinicSchedule}`
lock:WAIT
) {id}
checkDoctorAndOfficeFreeSlot: getClinicSchedule(
id:`find:
it.id == ${input.clinicSchedule} &&
!it.doctorScheduleList{cond = (
it.clinicDoctor.entityId == ${input.clinicDoctor.entityId}
|| it.clinicOffice.entityId == ${input.clinicOffice.entityId}
) && it.beginDate <= ${input.endDate} && it.endDate >= ${input.beginDate}
}.$exists`
failOnEmpty:true
) {id}
createDoctorSchedule(input: $input) {id}
}
}
3. Генерация конфигурации разрешений
Для автоматического создания файла permissions.json используется специальная конфигурация. Команда npm run allgen запускает процесс генерации, который в том числе включает в себя создание файла permissions.json с базовой структурой разрешений:
- Анализирует все GraphQL-файлы в папке
src/graphql/__generate/ - Создает JSON-описания для каждого запроса
- Формирует в корне React-приложения файл
permissions.jsonс базовой структурой разрешений
4. Настройка проверок безопасности в DataSpace CE
В файле graphql-permissions.json добавляется запись с условием проверки:
{
"name": "createDoctorSchedule",
"body": "mutation createDoctorSchedule($input: _CreateDoctorScheduleInput!) { ... }",
"checkSelects": [
{
"conditionValue": "'administrator' $in ${jwt:roles} || ${input.clinicDoctor} == ${jwt:clinicDoctorId}",
"typeName": null
}
],
"pathConditions": []
}
Это условие обеспечивает, что:
- Администратор может создавать расписание для любого врача
- Врач может создавать расписание только для себя (если его ID совпадает с ID в токене)
- Пациент не может выполнять данную операцию
5. Интеграция JWT-токенов в Apollo Client
При создании Apollo Client в React-приложении JWT-токен от Keycloak автоматически добавляется в заголовки всех GraphQL-запросов:
const apolloClient = new ApolloClient({
uri: '/graphql',
headers: {
"Authorization": `Bearer ${keycloak.token}`
}
});
6. Принцип работы проверки
Когда пользователь выполняет операцию создания расписания:
- React-приложение отправляет GraphQL-запрос с JWT-токеном
- DataSpace CE получает запрос и проверяет подпись токена с помощью ключей из
jwks.json - Извлекает роли пользователя из токена (
administrator,doctorилиpatient) - Выполняет проверку условия: может ли данный пользователь создать расписание для указанного врача
- Если проверка пройдена — операция выполняется, если нет — возвращается ошибка
403 Forbidden
7. Обработка ошибок авторизации в React
В компонентах React рекомендуется предусмотреть обработку ошибок авторизации:
const [createSchedule] = useCreateDoctorScheduleMutation({
onError: (error) => {
if (error.message.includes('403') || error.message.includes('Forbidden')) {
message.error('У вас недостаточно прав для выполнения данной операции');
}
}
});
Заключение
Данный подход обеспечивает многоуровневую защиту: аутентификация происходит в Keycloak, авторизация проверяется в DataSpace CE, а пользовательский интерфейс в React может дополнительно скрывать недоступные функции на основе ролей пользователя. Это создает надежную систему безопасности для медицинского приложения, где критически важно разграничить доступ между администраторами, врачами и пациентами.
Интеграция механизма фильтрации в React-приложение
Интеграция механизма фильтрации обеспечивает автоматическое ограничение данных, которые видит пользователь, в соответствии с его ролью и правами доступа. В отличие от проверок, фильтрация не блокирует запрос, а незаметно ограничивает возвращаемые данные.
1. Настройка конфигурации Keycloak
Используется та же конфигурация keycloak.json, что и для проверок, с важным параметром useResourceRoleMappings: true для работы с client roles:
{
"realm": "clinic",
"auth-server-url": "http://localhost:8180/",
"ssl-required": "external",
"resource": "clinic",
"public-client": true,
"confidential-port": 0,
"useResourceRoleMappings": true
}
2. Создание GraphQL-запроса для поиска записей врача
В приложении создается GraphQL-запрос для получения записей к врачу. Тело запроса должно точно соответствовать записи в graphql-permissions.json:
// src/graphql/__generate/searchDoctorAppointmentForDoctor.graphql
query searchDoctorAppointmentForDoctor($cond: String!) {
searchDoctorAppointment(cond: $cond) {
elems {
id
beginDate
endDate
clinicCustomer {
entity {
customer {
entity {
person {
entity {
firstName
lastName
birthDate
}
}
}
}
}
}
}
}
}
3. Генерация базовой конфигурации разрешений
Команда npm run allgen автоматически создает запись для данного запроса в файле permissions.json с базовой структурой, которая затем переносится в graphql-permissions.json DataSpace CE с добавлением условий фильтрации.
4. Настройка фильтрации в DataSpace CE
В файле graphql-permissions.json добавляется запись с условием фильтрации в секции pathConditions:
{
"name": "searchDoctorAppointmentForDoctor",
"body": "query searchDoctorAppointmentForDoctor($cond: String!) { ... }",
"checkSelects": [
{
"conditionValue": "'doctor' $in ${jwt:roles}",
"typeName": null
}
],
"pathConditions": [
{
"path": "searchDoctorAppointment",
"cond": "it.doctorSchedule.clinicDoctor.entityId == ${jwt:clinicDoctorId}"
}
]
}
Логика фильтрации:
- CheckSelects проверяет, что пользователь имеет роль
doctor - PathConditions автоматически ограничивает результаты только записями к данному врачу
${jwt:clinicDoctorId}— ID врача из JWT-токена, который сравнивается с ID врача в каждой записи
5. Принцип работы фильтрации
Когда врач выполняет поиск записей:
- React-приложение отправляет GraphQL-запрос с JWT-токеном и параметром поиска
- DataSpace CE проверяет роль пользователя (
doctor) - Автоматически добавляет условие фильтрации к запросу
- Возвращает только те записи, где
doctorSchedule.clinicDoctor.entityIdсовпадает с ID врача из токена - Врач видит только свои записи, даже если не указал это в параметре
cond
6. Использование в React-компонентах
В React-компоненте врач может искать записи, не беспокоясь о дополнительной фильтрации — система автоматически покажет только его записи:
const DoctorAppointments: React.FC = () => {
const [searchAppointments, { data, loading }] = useSearchDoctorAppointmentForDoctorLazyQuery();
const handleSearch = (searchText: string) => {
// Врач может искать по любым критериям (дата, имя пациента и т.д.)
// Система автоматически ограничит результаты только его записями
searchAppointments({
variables: {
cond: searchText // Например: "it.beginDate >= '2024-01-01'"
}
});
};
return (
<div>
<Input.Search
placeholder="Поиск записей..."
onSearch={handleSearch}
/>
{data?.searchDoctorAppointment.elems.map(appointment => (
<div key={appointment.id}>
{appointment.clinicCustomer.entity.customer.entity.person.entity.firstName}
{/* Отображение только записей данного врача */}
</div>
))}
</div>
);
};
7. Комбинирование с дополнительными условиями
Врач может добавлять свои условия поиска, которые комбинируются с автоматической фильтрацией:
// Врач ищет записи на завтра
const searchTomorrowAppointments = () => {
searchAppointments({
variables: {
cond: "it.beginDate >= '2024-01-15' && it.beginDate < '2024-01-16'"
}
});
};
// Система автоматически добавит:
// "it.doctorSchedule.clinicDoctor.entityId == ${jwt:clinicDoctorId}"
// Итоговое условие будет:
// "it.doctorSchedule.clinicDoctor.entityId == 'doctor123' && it.beginDate >= '2024-01-15' && it.beginDate < '2024-01-16'"
Заключение
В данном разделе мы рассмотрели основные современные механизмы обеспечения безопасности на примере проверок и фильтрации данных в комплексном приложении медицинской клиники.
Вы узнали:
- Как работает механизм авторизации и аутентификации в Keycloak
- Как настроить Keycloak для работы с DataSpace CE
- Как использовать механизмы проверки и фильтрации в GraphQL-запросах
- Как интегрировать механизмы безопасности в React-приложение
Также было показано, как использовать AI-ассистента GigaCode для доработки собственного варианта механизмов безопасности в своем приложении на примере медицинской клиники.
Данный раздел завершает учебный курс по быстрой разработке приложений с DataSpace Community Edition. Для большей эффективности рекомендуем еще раз ознакомиться с ключевыми материалами курса и попробовать выполнить доработку приложения медицинской клиники с помощью AI-ассистента GigaCode.
Благодарим за внимание и желаем успехов в дальнейшем обучении и практической работе!
Ссылки
- OpenID Connect Official Site
- Introduction to OpenID Connect
- JWT Official Site
- Keycloak Official Site
- Официальная документация React
- Официальная документация GraphQL
- Официальная документация Apollo GraphQL
